package org.mercworks.knight.game;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import org.lwjgl.Sys;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.glu.GLU;

import org.mercworks.knight.animation.AnimationManager;
import org.mercworks.knight.models.characters.Knight;

public class KnightGame
{
	private static final String GAME_TITLE = "My First Game";
	private static Knight knight;
	private static final int SIZE_FLOAT = 4;
	
	/** Desired frame time */
	private static final int FRAMERATE = 60;
	/** Exit the game */
	private static boolean finished;
	/** A rotating square! */
	// Lighting colors
	private static float faWhite[] = { 0.0f, 0.0f, 1.0f, 1.0f };
	private static float faLightBlue[] = { 0.8f, 0.8f, 0.9f, 1.0f };
	// Light direction: if last value is 0, then this describes light direction.
	// If 1, then light position.
	private static float lightDirection[] = { -2f, 2f, 2f, 0f };

	private static int width, height;
	//private static float angle;

	//private static IntBuffer buffers, sources;
	
	private static AnimationManager animationUtils;
	
	/**
	 * Application init
	 * @param args Commandline args
	 */
	public static void main(String[] args)
	{
		try
		{
			init();
			run();
		}
		catch (Exception e)
		{
			System.err.println(e.getMessage());
			Sys.alert(GAME_TITLE, "An error occured and the game will exit.");
		}
		finally
		{
			cleanup();
		}

		System.exit(0);
	}

	/**
	 * Initialize the game
	 * @throws Exception if init fails
	 */
	private static void init() throws Exception
	{
		// Create a fullscreen window with 1:1 orthographic 2D projection, and with
		// mouse, keyboard, and gamepad inputs.
		Display.setTitle(GAME_TITLE);
		Display.setFullscreen(true);
		// Enable vsync if we can
		Display.setVSyncEnabled(true);
		Display.create();

		// Start up the sound system
		//AL.create();
		//initSound();

		width = Display.getDisplayMode().getWidth();
		height = Display.getDisplayMode().getHeight();

		GL11.glViewport(0, 0, width, height);
		GL11.glEnable(GL11.GL_DEPTH_TEST);
		GL11.glMatrixMode(GL11.GL_PROJECTION);
		GL11.glLoadIdentity();
		GLU.gluPerspective(45.0f, (float) width / (float) height, .01f, 1000.0f);

		setLight(GL11.GL_LIGHT1, faWhite, faLightBlue, lightDirection);
		GL11.glEnable(GL11.GL_LIGHTING);

		//KnightWorldPhysics.initKnightWorldPhysics();
		knight = new Knight();
		animationUtils = new AnimationManager();
		
		GL11.glEnable(GL11.GL_TEXTURE_2D);

		GL11.glMatrixMode(GL11.GL_MODELVIEW);
		GL11.glLoadIdentity();
	}

	/**
	 * Runs the game (the "main loop")
	 */
	private static void run()
	{
		while (!finished)
		{
			// Always call Window.update(), all the time
			Display.update();

			if (Display.isCloseRequested())
			{
				// Check for O/S close requests
				finished = true;
			}
			else if (Display.isActive())
			{
				// The window is in the foreground, so we should play the game
				logic();
				render();
				Display.sync(FRAMERATE);
			}
			else
			{
				// The window is not in the foreground, so we can allow other stuff to
				// run and
				// infrequently update
				try
				{
					Thread.sleep(100);
				}
				catch (InterruptedException e)
				{
				}
				logic();
				if (Display.isVisible() || Display.isDirty())
				{
					// Only bother rendering if the window is visible or dirty
					render();
				}
			}
		}
	}

	/**
	 * Do any game-specific cleanup
	 */
	private static void cleanup()
	{
		//cleanup physics
		//KnightWorldPhysics.getCollisionSpaceInstance().delete();
		//KnightWorldPhysics.getCollisionCalcsInstance().delete();
		//KnightWorldPhysics.getWorldInstance().delete();
		//Ode.dCloseODE();
		
		// Stop the sound
		//AL.destroy();

		// Close the window
		Display.destroy();
	}

	/**
	 * Do all calculations, handle input, etc.
	 */
	private static void logic()
	{
		/** TODO - Add the game logic **/
		if(Keyboard.isKeyDown(Keyboard.KEY_RIGHT))
		{
			animationUtils.playAnimation(knight, 0);
		}
		
		if(Keyboard.isKeyDown(Keyboard.KEY_ESCAPE))
		{
			finished = true;
		}
	}

	/**
	 * Render the current frame
	 */
	private static void render()
	{
		GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
		GL11.glLoadIdentity();
		GLU.gluLookAt(0, 500, 50, 0, 0, 0, 0, 1, 0);
		
		GL11.glPushMatrix();

			//GL11.glRotatef(-90 % 360, 1.0f, 0.0f, 0.0f);
			GL11.glTranslatef(knight.getXPos(), knight.getYPos(), knight.getZPos());
			GL11.glRotatef(knight.getXRot(), 1.0f, 0.0f, 0.0f);
			GL11.glRotatef(knight.getYRot(), 0.0f, 1.0f, 0.0f);
			GL11.glRotatef(knight.getZRot(), 0.0f, 0.0f, 1.0f);
			knight.render();
		GL11.glPopMatrix();
	}

	/**
	 * Set the color of a 'positional' light (a light that has a specific position
	 * within the scene). <BR>
	 * 
	 * Pass in an OpenGL light number (GL11.GL_LIGHT1), the 'Diffuse' and
	 * 'Ambient' colors (direct light and reflected light), and the position.<BR>
	 * 
	 * @param GLLightHandle
	 * @param diffuseLightColor
	 * @param ambientLightColor
	 * @param position
	 */
	public static void setLight(int GLLightHandle, float[] diffuseLightColor,
			float[] ambientLightColor, float[] position)
	{
		FloatBuffer ltDiffuse = allocFloats(diffuseLightColor);
		FloatBuffer ltAmbient = allocFloats(ambientLightColor);
		FloatBuffer ltPosition = allocFloats(position);
		GL11.glLight(GLLightHandle, GL11.GL_DIFFUSE, ltDiffuse); // color of the
		// direct
		// illumination
		GL11.glLight(GLLightHandle, GL11.GL_SPECULAR, ltDiffuse); // color of the
		// highlight
		GL11.glLight(GLLightHandle, GL11.GL_AMBIENT, ltAmbient); // color of the
		// reflected light
		GL11.glLight(GLLightHandle, GL11.GL_POSITION, ltPosition);
		GL11.glEnable(GLLightHandle); // Enable the light (GL_LIGHT1 - 7)
		// GL11.glLightf(GLLightHandle, GL11.GL_QUADRATIC_ATTENUATION, .005F); //
		// how light beam drops off
	}
	
	public static FloatBuffer allocFloats(float[] floatarray)
	{
		FloatBuffer fb = ByteBuffer.allocateDirect(floatarray.length * SIZE_FLOAT)
				.order(ByteOrder.nativeOrder()).asFloatBuffer();
		fb.put(floatarray).flip();
		return fb;
	}



	/*private static void initSound()
	{
		int lastError;

		// create 1 buffer and 1 source
		buffers = BufferUtils.createIntBuffer(1);
		sources = BufferUtils.createIntBuffer(1);

		// al generate buffers and sources
		buffers.position(0).limit(1);
		AL10.alGenBuffers(buffers);
		if ((lastError = AL10.alGetError()) != AL10.AL_NO_ERROR)
		{
			exit(lastError);
		}

		sources.position(0).limit(1);
		AL10.alGenSources(sources);
		if ((lastError = AL10.alGetError()) != AL10.AL_NO_ERROR)
		{
			exit(lastError);
		}

		// load wave data
		WaveData wavefile = WaveData.create(KnightGame.class.getResource("/sounds/footstep.wav"));

		// copy to buffers
		AL10.alBufferData(buffers.get(0), wavefile.format, wavefile.data,
				wavefile.samplerate);
		if ((lastError = AL10.alGetError()) != AL10.AL_NO_ERROR)
		{
			exit(lastError);
		}

		// unload file again
		wavefile.dispose();

		// set up source input
		AL10.alSourcei(sources.get(0), AL10.AL_BUFFER, buffers.get(0));
		if ((lastError = AL10.alGetError()) != AL10.AL_NO_ERROR)
		{
			exit(lastError);
		}
		
		AL10.alSourcef(sources.get(0), AL10.AL_REFERENCE_DISTANCE, 1024.0f);
		AL10.alSourcef(sources.get(0), AL10.AL_ROLLOFF_FACTOR, 0.5f);
	}*/

	/**
	 * Exits the test NOW, printing errorcode to stdout
	 * @param error Error code causing exit
	 */
	/*private static void exit(int error)
	{
		System.out.println("OpenAL Error: " + AL10.alGetString(error));
		alExit();
		System.exit(-1);
	}*/
	
	/**
	 * Shutdowns OpenAL
	 */
	/*private static void alExit()
	{
		if (AL.isCreated())
		{
			AL.destroy();
		}
	}*/
}
